/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.runtime

import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentHashMapOf
import androidx.compose.runtime.mock.EmptyApplier
import androidx.compose.runtime.mock.Linear
import androidx.compose.runtime.mock.MockViewValidator
import androidx.compose.runtime.mock.TestMonotonicFrameClock
import androidx.compose.runtime.mock.Text
import androidx.compose.runtime.mock.compositionTest
import androidx.compose.runtime.mock.expectChanges
import androidx.compose.runtime.mock.expectNoChanges
import androidx.compose.runtime.mock.revalidate
import androidx.compose.runtime.mock.validate
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch

// Create a normal (dynamic) CompositionLocal with a string value
val LocalSomeTextComposition = compositionLocalOf { "Default" }

// Create a normal (dynamic) CompositionLocal with an int value
val LocalSomeIntComposition = compositionLocalOf { 1 }

// Create a non-overridable CompositionLocal provider key
private val LocalSomeOtherIntProvider = compositionLocalOf { 1 }

// Make public the consumer key.
val LocalSomeOtherIntComposition: CompositionLocal<Int> = LocalSomeOtherIntProvider

// Create a static CompositionLocal with an int value
val LocalSomeStaticInt = staticCompositionLocalOf { 40 }

@Stable
class CompositionLocalTests {

    @Composable
    fun ReadStringCompositionLocal(compositionLocal: CompositionLocal<String>) {
        Text(value = compositionLocal.current)
    }

    @Test
    fun testCompositionLocalApi() = compositionTest {
        compose {
            assertEquals("Default", LocalSomeTextComposition.current)
            assertEquals(1, LocalSomeIntComposition.current)
            CompositionLocalProvider(
                LocalSomeTextComposition provides "Test1",
                LocalSomeIntComposition provides 12,
                LocalSomeOtherIntProvider provides 42,
                LocalSomeStaticInt provides 50,
            ) {
                assertEquals("Test1", LocalSomeTextComposition.current)
                assertEquals(12, LocalSomeIntComposition.current)
                assertEquals(42, LocalSomeOtherIntComposition.current)
                assertEquals(50, LocalSomeStaticInt.current)
                CompositionLocalProvider(
                    LocalSomeTextComposition provides "Test2",
                    LocalSomeStaticInt provides 60,
                ) {
                    assertEquals("Test2", LocalSomeTextComposition.current)
                    assertEquals(12, LocalSomeIntComposition.current)
                    assertEquals(42, LocalSomeOtherIntComposition.current)
                    assertEquals(60, LocalSomeStaticInt.current)
                }
                assertEquals("Test1", LocalSomeTextComposition.current)
                assertEquals(12, LocalSomeIntComposition.current)
                assertEquals(42, LocalSomeOtherIntComposition.current)
                assertEquals(50, LocalSomeStaticInt.current)
            }
            assertEquals("Default", LocalSomeTextComposition.current)
            assertEquals(1, LocalSomeIntComposition.current)
        }
    }

    @Test
    fun recompose_Dynamic() = compositionTest {
        val invalidates = mutableListOf<RecomposeScope>()
        fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
        var someText = "Unmodified"
        compose {
            invalidates.add(currentRecomposeScope)
            CompositionLocalProvider(LocalSomeTextComposition provides someText) {
                ReadStringCompositionLocal(compositionLocal = LocalSomeTextComposition)
            }
        }

        fun validate() {
            validate { Text(someText) }
        }

        someText = "Modified"
        doInvalidate()
        expectChanges()
        validate()
    }

    @Test
    fun recompose_Static() = compositionTest {
        val invalidates = mutableListOf<RecomposeScope>()
        fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
        val staticStringCompositionLocal = staticCompositionLocalOf { "Default" }
        var someText = "Unmodified"
        compose {
            invalidates.add(currentRecomposeScope)
            CompositionLocalProvider(staticStringCompositionLocal provides someText) {
                ReadStringCompositionLocal(compositionLocal = staticStringCompositionLocal)
            }
        }

        fun validate() {
            validate { Text(someText) }
        }
        validate()

        someText = "Modified"
        doInvalidate()
        expectChanges()
        validate()
    }

    @Test
    fun subCompose_Dynamic() = compositionTest {
        val invalidates = mutableListOf<RecomposeScope>()
        fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
        var someText = "Unmodified"
        compose {
            invalidates.add(currentRecomposeScope)

            CompositionLocalProvider(
                LocalSomeTextComposition provides someText,
                LocalSomeIntComposition provides 0,
            ) {
                ReadStringCompositionLocal(compositionLocal = LocalSomeTextComposition)

                TestSubcomposition {
                    assertEquals(someText, LocalSomeTextComposition.current)
                    assertEquals(0, LocalSomeIntComposition.current)

                    CompositionLocalProvider(LocalSomeIntComposition provides 42) {
                        assertEquals(someText, LocalSomeTextComposition.current)
                        assertEquals(42, LocalSomeIntComposition.current)
                    }
                }
            }
        }

        fun validate() {
            validate { Text(someText) }
        }

        someText = "Modified"
        doInvalidate()
        expectChanges()
        validate()
    }

    @Test
    fun subCompose_Static() = compositionTest {
        val invalidates = mutableListOf<RecomposeScope>()
        fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
        val staticSomeTextCompositionLocal = staticCompositionLocalOf { "Default" }
        val staticSomeIntCompositionLocal = staticCompositionLocalOf { -1 }
        var someText = "Unmodified"
        compose {
            invalidates.add(currentRecomposeScope)

            CompositionLocalProvider(
                staticSomeTextCompositionLocal provides someText,
                staticSomeIntCompositionLocal provides 0,
            ) {
                assertEquals(someText, staticSomeTextCompositionLocal.current)
                assertEquals(0, staticSomeIntCompositionLocal.current)

                ReadStringCompositionLocal(compositionLocal = staticSomeTextCompositionLocal)

                TestSubcomposition {
                    assertEquals(someText, staticSomeTextCompositionLocal.current)
                    assertEquals(0, staticSomeIntCompositionLocal.current)

                    CompositionLocalProvider(staticSomeIntCompositionLocal provides 42) {
                        assertEquals(someText, staticSomeTextCompositionLocal.current)
                        assertEquals(42, staticSomeIntCompositionLocal.current)
                    }
                }
            }
        }

        fun validate() {
            validate { Text(someText) }
        }

        someText = "Modified"
        doInvalidate()
        expectChanges()
        validate()
    }

    @Test
    fun deferredSubCompose_Dynamic() = compositionTest {
        val invalidates = mutableListOf<RecomposeScope>()
        fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
        var someText = "Unmodified"
        var doSubCompose: () -> Unit = { error("Sub-compose callback not set") }
        compose {
            invalidates.add(currentRecomposeScope)

            CompositionLocalProvider(
                LocalSomeTextComposition provides someText,
                LocalSomeIntComposition provides 0,
            ) {
                ReadStringCompositionLocal(compositionLocal = LocalSomeTextComposition)

                doSubCompose = testDeferredSubcomposition {
                    assertEquals(someText, LocalSomeTextComposition.current)
                    assertEquals(0, LocalSomeIntComposition.current)

                    CompositionLocalProvider(LocalSomeIntComposition provides 42) {
                        assertEquals(someText, LocalSomeTextComposition.current)
                        assertEquals(42, LocalSomeIntComposition.current)
                    }
                }
            }
        }

        fun validate() {
            validate { Text(someText) }
        }
        validate()
        doSubCompose()

        someText = "Modified"
        doInvalidate()
        expectChanges()
        validate()

        doSubCompose()
    }

    @Test
    fun deferredSubCompose_Static() = compositionTest {
        val invalidates = mutableListOf<RecomposeScope>()
        fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
        var someText = "Unmodified"
        var doSubCompose: () -> Unit = { error("Sub-compose callback not set") }
        val staticSomeTextCompositionLocal = staticCompositionLocalOf { "Default" }
        val staticSomeIntCompositionLocal = staticCompositionLocalOf { -1 }
        compose {
            invalidates.add(currentRecomposeScope)

            CompositionLocalProvider(
                staticSomeTextCompositionLocal provides someText,
                staticSomeIntCompositionLocal provides 0,
            ) {
                assertEquals(someText, staticSomeTextCompositionLocal.current)
                assertEquals(0, staticSomeIntCompositionLocal.current)

                ReadStringCompositionLocal(compositionLocal = staticSomeTextCompositionLocal)

                doSubCompose = testDeferredSubcomposition {
                    assertEquals(someText, staticSomeTextCompositionLocal.current)
                    assertEquals(0, staticSomeIntCompositionLocal.current)

                    CompositionLocalProvider(staticSomeIntCompositionLocal provides 42) {
                        assertEquals(someText, staticSomeTextCompositionLocal.current)
                        assertEquals(42, staticSomeIntCompositionLocal.current)
                    }
                }
            }
        }

        fun validate() {
            validate { Text(someText) }
        }
        validate()
        doSubCompose()

        someText = "Modified"
        doInvalidate()
        expectChanges()
        validate()

        doSubCompose()
    }

    @Test
    fun deferredSubCompose_Nested_Static() = compositionTest {
        val invalidates = mutableListOf<RecomposeScope>()
        fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
        var someText = "Unmodified"
        var doSubCompose1: () -> Unit = { error("Sub-compose-1 callback not set") }
        var doSubCompose2: () -> Unit = { error("Sub-compose-2 callback not set") }
        val staticSomeTextCompositionLocal = staticCompositionLocalOf { "Default" }
        val staticSomeIntCompositionLocal = staticCompositionLocalOf { -1 }
        compose {
            invalidates.add(currentRecomposeScope)

            CompositionLocalProvider(
                staticSomeTextCompositionLocal provides someText,
                staticSomeIntCompositionLocal provides 0,
            ) {
                assertEquals(someText, staticSomeTextCompositionLocal.current)
                assertEquals(0, staticSomeIntCompositionLocal.current)

                ReadStringCompositionLocal(compositionLocal = staticSomeTextCompositionLocal)

                doSubCompose1 = testDeferredSubcomposition {
                    assertEquals(someText, staticSomeTextCompositionLocal.current)
                    assertEquals(0, staticSomeIntCompositionLocal.current)

                    doSubCompose2 = testDeferredSubcomposition {
                        assertEquals(someText, staticSomeTextCompositionLocal.current)
                        assertEquals(0, staticSomeIntCompositionLocal.current)
                    }
                }
            }
        }

        fun validate() {
            validate { Text(someText) }
        }
        validate()
        doSubCompose1()
        doSubCompose2()

        someText = "Modified"
        doInvalidate()
        expectChanges()
        validate()

        doSubCompose1()
        doSubCompose2()
    }

    @Test
    fun insertShouldSeePreviouslyProvidedValues() = compositionTest {
        val invalidates = mutableListOf<RecomposeScope>()
        fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
        val someStaticString = staticCompositionLocalOf { "Default" }
        var shouldRead = false
        compose {
            CompositionLocalProvider(someStaticString provides "Provided A") {
                invalidates.add(currentRecomposeScope)
                if (shouldRead) ReadStringCompositionLocal(someStaticString)
            }
        }

        fun validate() {
            validate { if (shouldRead) Text("Provided A") }
        }
        validate()

        shouldRead = true
        doInvalidate()
        expectChanges()
        validate()
    }

    @Composable
    fun ReadSomeDataCompositionLocal(
        compositionLocal: CompositionLocal<SomeData>,
        composed: StableRef<Boolean>,
    ) {
        composed.value = true
        Text(value = compositionLocal.current.value)
    }

    @Test
    fun providingANewDataClassValueShouldNotRecompose() = compositionTest {
        val invalidates = mutableListOf<RecomposeScope>()
        fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
        val someDataCompositionLocal = compositionLocalOf(structuralEqualityPolicy()) { SomeData() }
        val composed = StableRef(false)

        compose {
            invalidates.add(currentRecomposeScope)
            CompositionLocalProvider(someDataCompositionLocal provides SomeData("provided")) {
                ReadSomeDataCompositionLocal(someDataCompositionLocal, composed)
            }
        }

        assertTrue(composed.value)
        composed.value = false
        doInvalidate()
        expectNoChanges()
        assertFalse(composed.value)
    }

    @Test // Regression test for b/186094122
    fun currentRetrievesCorrectValues() = compositionTest {
        val state = mutableStateOf(0)
        val providedValue = mutableStateOf(100)
        val local = compositionLocalOf { 0 }
        val static = staticCompositionLocalOf { -1 }
        val providedStatic = mutableStateOf(2000)
        var includeProviders by mutableStateOf(true)

        @Composable
        fun Validate(providedValue: Int) {
            val currentValue = local.current
            assertEquals(providedValue, currentValue)
            state.value // Observe state for invalidates.
        }

        compose {
            if (includeProviders) {
                CompositionLocalProvider(
                    local provides providedValue.value,
                    static provides providedStatic.value,
                ) {
                    Validate(providedValue.value)
                }
            }
        }

        // Force an providerUpdates entry to be created.
        providedStatic.value++
        advance()

        // Force a new provider scope to be created
        includeProviders = false
        advance()

        includeProviders = true
        advance()

        // Change the provided value
        providedValue.value++
        advance()

        // Ensure the old providerUpdates is not longer contains old values.
        state.value++
        advance()
    }

    @Test // Regression test for b/193433239
    fun canProvideManyProvidersSimultaneously() {
        val locals = (1..1000).map { compositionLocalOf { 2 } }
        val LocalTest = compositionLocalOf<Int> { error("") }

        @Composable
        fun Test() {
            CompositionLocalProvider(LocalTest provides 1) {
                CompositionLocalProvider(
                    *locals.map { it provides 3 }.toTypedArray(),
                    LocalTest provides 2,
                ) {
                    assertEquals(2, LocalTest.current)
                }
            }
        }
    }

    @Test // Regression test for b/193433239
    fun testTheUnderlyingPropertiesOfPersistentHashMap() {
        val p = persistentHashMapOf<Int, Int>(99 to 1)
        val e = Array(101) { it }.map { it to it }
        val c = persistentHashMapOf(*e.toTypedArray())
        val n = p.builder().apply { putAll(c) }.build()
        repeat(101) { assertEquals(it, n[it]) }
    }

    @Suppress("UNUSED_EXPRESSION")
    @Test // Regression for b/292224893
    fun testSingleInvalidatedProvider() = compositionTest {
        val local1 = compositionLocalOf { 10 }
        val local2 = compositionLocalOf { 20 }
        val local3 = compositionLocalOf { 30 }
        var state by mutableStateOf(0)

        compose {
            state
            CompositionLocalProvider(local1 provides 11) {
                state
                CompositionLocalProvider(local2 provides 22) {
                    state
                    CompositionLocalProvider(local3 provides 33) {
                        state
                        assertEquals(11, local1.current)
                        assertEquals(22, local2.current)
                        assertEquals(33, local3.current)
                    }
                    assertEquals(11, local1.current)
                    assertEquals(22, local2.current)
                    assertEquals(30, local3.current)
                }
                assertEquals(11, local1.current)
                assertEquals(20, local2.current)
                assertEquals(30, local3.current)
            }
            assertEquals(10, local1.current)
            assertEquals(20, local2.current)
            assertEquals(30, local3.current)
        }

        state++

        advance()
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun testProvideAllLocals() = compositionTest {
        val local1 = compositionLocalOf { 0 }
        val local2 = compositionLocalOf { 0 }
        val staticLocal = staticCompositionLocalOf { 0 }

        var provided: Array<ProvidedValue<Int>> by mutableStateOf(emptyArray())

        var actualValues = emptySet<Any>()

        @Composable
        fun LocalsConsumer() {
            val locals by rememberUpdatedState(currentCompositionLocalContext)
            DisposableEffect(Unit) {
                val context = coroutineContext + TestMonotonicFrameClock(this@compositionTest)
                val recomposer = Recomposer(context)
                launch(context) { recomposer.runRecomposeAndApplyChanges() }
                val composition2 = Composition(EmptyApplier(), recomposer)
                composition2.setContent {
                    CompositionLocalProvider(locals) {
                        actualValues = setOf(local1.current, local2.current, staticLocal.current)
                    }
                }
                onDispose {
                    composition2.dispose()
                    recomposer.cancel()
                }
            }
        }

        compose { CompositionLocalProvider(*provided) { LocalsConsumer() } }

        advance()
        assertEquals(setOf(0, 0, 0), actualValues)

        provided = arrayOf(local1 provides 1)
        advance()
        assertEquals(setOf(1, 0, 0), actualValues)

        provided = arrayOf(local1 provides 2)
        advance()
        assertEquals(setOf(2, 0, 0), actualValues)

        provided = arrayOf(local1 provides 2, staticLocal provides 1)
        advance()
        assertEquals(setOf(2, 0, 1), actualValues)

        provided = arrayOf(local1 provides 2, staticLocal provides 2)
        advance()
        assertEquals(setOf(2, 0, 2), actualValues)

        provided = arrayOf(local1 provides 1, staticLocal provides 1)
        advance()
        assertEquals(setOf(1, 0, 1), actualValues)

        provided = arrayOf(local1 provides 1, local2 provides 1, staticLocal provides 1)
        advance()
        assertEquals(setOf(1, 1, 1), actualValues)

        provided = arrayOf(local1 provides 1, staticLocal provides 1)
        advance()
        assertEquals(setOf(1, 0, 1), actualValues)

        provided = emptyArray()
        advance()
        assertEquals(setOf(0, 0, 0), actualValues)
    }

    @Test // Regression test for b/233064044
    fun testRecomposeCacheInvalidation() = compositionTest {
        var state = mutableStateOf(0)

        compose { CacheInvalidate(state) }

        validate { this.CacheInvalidate(state) }

        state.value++
        advance()

        revalidate()
    }

    @Test // Regression test for: b/330036209
    fun testSingleProvideDefaultValue() = compositionTest {
        val local = compositionLocalOf { 0 }
        compose {
            CompositionLocalProvider(local provides 1) {
                CompositionLocalProvider(local providesDefault 2) { assertEquals(1, local.current) }
            }
        }
    }

    @Test
    fun testDefaultComputedLocal() = compositionTest {
        val local1 = compositionLocalOf { 0 }
        val local2 = compositionLocalWithComputedDefaultOf { local1.currentValue + 10 }
        compose {
            val valueOfLocal1 = local1.current
            val valueOfLocal2 = local2.current
            assertEquals(valueOfLocal1 + 10, valueOfLocal2)
            CompositionLocalProvider(local1 provides 20) {
                val nestedValueOfLocal1 = local1.current
                val nestedValueOfLocal2 = local2.current
                assertEquals(nestedValueOfLocal1 + 10, nestedValueOfLocal2)
            }
        }
    }

    @Test
    fun testProvidingDynamicLocalAsComputed() = compositionTest {
        val local1 = compositionLocalOf { 0 }
        val local2 = compositionLocalOf { 0 }
        compose {
            val valueOfLocal1A = local1.current
            val valueOfLocal2A = local2.current
            assertEquals(0, valueOfLocal1A)
            assertEquals(0, valueOfLocal2A)
            CompositionLocalProvider(local2 providesComputed { local1.currentValue + 10 }) {
                val valueOfLocal1B = local1.current
                val valueOfLocal2B = local2.current
                assertEquals(valueOfLocal1B + 10, valueOfLocal2B)
                CompositionLocalProvider(local1 provides 10) {
                    val valueOfLocal1C = local1.current
                    val valueOfLocal2C = local2.current
                    assertEquals(valueOfLocal1C + 10, valueOfLocal2C)
                }
            }
        }
    }

    @Test
    fun testProvidingAStaticLocalAsComputed() = compositionTest {
        val local1 = staticCompositionLocalOf { 0 }
        val local2 = staticCompositionLocalOf { 0 }
        compose {
            val valueOfLocal1A = local1.current
            val valueOfLocal2A = local2.current
            assertEquals(0, valueOfLocal1A)
            assertEquals(0, valueOfLocal2A)
            CompositionLocalProvider(local2 providesComputed { local1.currentValue + 10 }) {
                val valueOfLocal1B = local1.current
                val valueOfLocal2B = local2.current
                assertEquals(valueOfLocal1B + 10, valueOfLocal2B)
                CompositionLocalProvider(local1 provides 10) {
                    val valueOfLocal1C = local1.current
                    val valueOfLocal2C = local2.current
                    assertEquals(valueOfLocal1C + 10, valueOfLocal2C)
                }
            }
        }
    }

    @Test
    fun testValueChangeWhileRemoving() = compositionTest {
        val LocalText = compositionLocalOf { "" }
        var showContent by mutableStateOf(true)
        var text by mutableStateOf("Hello")

        compose {
            if (showContent) {
                Linear {
                    CompositionLocalProvider(LocalText provides text) { Text(LocalText.current) }
                }
            }
        }

        validate { Linear { Text("Hello") } }

        text = "Goodbye"
        showContent = false
        expectChanges()

        validate { /* Empty Composition */ }
    }

    @Suppress("LocalVariableName")
    @Test
    // Validate androidx.compose.runtime.samples.compositionLocalComputedAfterProvidingLocal
    fun validateSampledExample() = compositionTest {
        val LocalValue = compositionLocalOf { 10 }
        val LocalLargerValue = compositionLocalOf { 12 }
        val LocalComputedValue = compositionLocalWithComputedDefaultOf {
            LocalValue.currentValue + 4
        }

        @Composable
        fun App() {
            // Value is 10, the default value for LocalValue
            val value = LocalValue.current
            assertEquals(10, value)
            // Value is 12, the default value
            val largerValue = LocalLargerValue.current
            assertEquals(12, largerValue)
            // Value is computed to be 14
            val computedValue = LocalComputedValue.current
            assertEquals(14, computedValue)
            CompositionLocalProvider(LocalValue provides 20) {
                // Value is 20 provided above
                val nestedValue = LocalValue.current
                assertEquals(20, nestedValue)
                // Value is still 12 as an updated value was not re-provided
                val nestedLargerValue = LocalLargerValue.current
                assertEquals(12, nestedLargerValue)
                // Values is computed to be 24; LocalValue.current + 4
                val nestedComputedValue = LocalComputedValue.current
                assertEquals(24, nestedComputedValue)
                CompositionLocalProvider(LocalLargerValue provides LocalValue.current + 2) {
                    // Value is 22 provided above
                    val newLargerValue = LocalLargerValue.current
                    assertEquals(22, newLargerValue)
                    CompositionLocalProvider(LocalValue provides 50) {
                        // Value is now 50 provided above
                        val finalValue = LocalValue.current
                        assertEquals(50, finalValue)
                        // Value is still 22
                        val finalLargerValue = LocalLargerValue.current
                        assertEquals(22, finalLargerValue)
                        // Value is now computed to be 54
                        val finalComputed = LocalComputedValue.current
                        assertEquals(54, finalComputed)
                    }
                }
            }
        }

        compose { App() }
    }

    @Test // 374263387
    fun staticLocalUpdateInvalidatesCorrectly_startProvide() = compositionTest {
        val LocalValue = staticCompositionLocalOf<Boolean> { error("Not provided") }
        val LocalOtherValue = staticCompositionLocalOf<Int> { error("Not provided") }
        var value by mutableStateOf(false)
        var valueSeen = false
        compose {
            CompositionLocalProvider(LocalValue provides value) {
                CompositionLocalProvider(LocalOtherValue providesDefault 1) {
                    CompositionLocalProvider(LocalOtherValue providesDefault 2) {
                        valueSeen = LocalValue.current
                    }
                }
            }
        }
        assertFalse(valueSeen)
        value = true
        advance()
        assertTrue(valueSeen)
    }

    @Test
    fun testProvidesCompositionLocalsAndReturnResult() = compositionTest {
        val expected = "expected"

        compose {
            assertEquals("Default", LocalSomeTextComposition.current)
            assertEquals(1, LocalSomeIntComposition.current)
            val result =
                withCompositionLocals(
                    LocalSomeTextComposition provides "Test1",
                    LocalSomeIntComposition provides 12,
                    LocalSomeOtherIntProvider provides 42,
                    LocalSomeStaticInt provides 50,
                ) {
                    assertEquals("Test1", LocalSomeTextComposition.current)
                    assertEquals(12, LocalSomeIntComposition.current)
                    assertEquals(42, LocalSomeOtherIntComposition.current)
                    assertEquals(50, LocalSomeStaticInt.current)
                    CompositionLocalProvider(
                        LocalSomeTextComposition provides "Test2",
                        LocalSomeStaticInt provides 60,
                    ) {
                        assertEquals("Test2", LocalSomeTextComposition.current)
                        assertEquals(12, LocalSomeIntComposition.current)
                        assertEquals(42, LocalSomeOtherIntComposition.current)
                        assertEquals(60, LocalSomeStaticInt.current)
                    }
                    assertEquals("Test1", LocalSomeTextComposition.current)
                    assertEquals(12, LocalSomeIntComposition.current)
                    assertEquals(42, LocalSomeOtherIntComposition.current)
                    assertEquals(50, LocalSomeStaticInt.current)

                    return@withCompositionLocals expected
                }
            assertEquals("Default", LocalSomeTextComposition.current)
            assertEquals(1, LocalSomeIntComposition.current)
            assertEquals(expected, result)
        }
    }

    @Test
    fun testProvidesSingleCompositionLocalAndReturnsResult() = compositionTest {
        val expected = "expected"

        compose {
            assertEquals("Default", LocalSomeTextComposition.current)
            val result =
                withCompositionLocal(LocalSomeTextComposition provides "Test1") {
                    assertEquals("Test1", LocalSomeTextComposition.current)
                    CompositionLocalProvider(LocalSomeTextComposition provides "Test2") {
                        assertEquals("Test2", LocalSomeTextComposition.current)
                    }
                    assertEquals("Test1", LocalSomeTextComposition.current)

                    return@withCompositionLocal expected
                }
            assertEquals("Default", LocalSomeTextComposition.current)
            assertEquals(expected, result)
        }
    }

    @Test
    fun testProvidesSingleCompositionLocalAndReturnsNestedResults() = compositionTest {
        val expected = "expected"

        compose {
            assertEquals("Default", LocalSomeTextComposition.current)
            val result =
                withCompositionLocal(LocalSomeTextComposition provides "Test1") {
                    withCompositionLocal(LocalSomeTextComposition provides "Test2") {
                        assertEquals("Test2", LocalSomeTextComposition.current)
                        expected
                    }
                }
            assertEquals("Default", LocalSomeTextComposition.current)
            assertEquals(expected, result)
        }
    }

    fun staticLocalUpdateInvalidatesCorrectly_startProvides() = compositionTest {
        val SomeValue = staticCompositionLocalOf { 0 }
        val LocalValue = staticCompositionLocalOf<Boolean> { error("Not provided") }
        val LocalOtherValue = staticCompositionLocalOf<Int> { error("Not provided") }
        var value by mutableStateOf(false)
        var valueSeen = false
        compose {
            CompositionLocalProvider(SomeValue provides 0, LocalValue provides value) {
                CompositionLocalProvider(SomeValue provides 1, LocalOtherValue providesDefault 1) {
                    CompositionLocalProvider(
                        SomeValue provides 2,
                        LocalOtherValue providesDefault 2,
                    ) {
                        valueSeen = LocalValue.current
                    }
                }
            }
        }
        assertFalse(valueSeen)
        value = true
        advance()
        assertTrue(valueSeen)
    }
}

val LocalCache = staticCompositionLocalOf { "Unset" }

@Composable
fun CacheInvalidate(state: State<Int>) {
    Text("${state.value}")
    Text(LocalCache.current)
    CacheInvalidateSet {
        Text("${state.value}")
        Text(LocalCache.current)
    }
    Text(LocalCache.current)
}

@Composable
fun CacheInvalidateSet(content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalCache provides "Set") { content() }
}

fun MockViewValidator.CacheInvalidate(state: State<Int>) {
    Text("${state.value}")
    Text("Unset")
    Text("${state.value}")
    Text("Set")
    Text("Unset")
}

data class SomeData(val value: String = "default")

@Stable class StableRef<T>(var value: T)
